﻿using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Networking;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;


namespace HomeControllerServer
{
    public class TcpHelp : IDisposable
    {
        const int TIMEOUT = 30000;
        const int MAX_BUFFER_LENGTH = 20 * 1024;

        DataReader read;
        DataWriter write;

        StreamSocket socketClient;             //for client
        StreamSocket socketServer;             //for server
        StreamSocketListener socketListener;   //for server

        public event EventHandler<ClientConnectEventArgs> ClientConnectEvent;
        public event EventHandler<ClientSendEventArgs> ClientSendEvent;

        public void Dispose()
        {
            lock (this)
            {
                if (read != null)
                {
                    try
                    {
                        read.DetachStream();
                        read = null;
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine(ex.Message);
                    }
                }

                if (write != null)
                {
                    try
                    {
                        write.DetachStream();
                        write = null;
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine(ex.Message);
                    }
                }

                if (socketClient != null)
                {
                    socketClient.Dispose();
                    socketClient = null;
                }

                if (socketServer != null)
                {
                    socketServer.Dispose();
                    socketServer = null;
                }

                if (socketListener != null)
                {
                    socketListener.Dispose();
                    socketListener = null;
                }
            }
        }

        public async Task<bool> Connect(string serverHostName, string serverPort)
        {
            bool result = false;
            socketClient = new StreamSocket();
            try
            {
                HostName hostName = new HostName(serverHostName);
                await socketClient.ConnectAsync(hostName, serverPort);
                Debug.WriteLine("Connected");
                result = true;
                PostSocketReadForClient(MAX_BUFFER_LENGTH);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            return result;
        }

        public async Task<bool> Listen(string port)
        {
            bool result = false;
            if (socketListener == null)
            {
                socketListener = new StreamSocketListener();
            }
            try
            {
                socketListener.ConnectionReceived += (op, evt) =>
                {
                    // For simplicity, the server can talk to only one client at a time.
                    socketServer = evt.Socket;
                    if (write != null)
                    {
                        write.DetachStream();
                        write = null;
                    }
                    if (null != ClientConnectEvent)
                    {
                        ClientConnectEvent(null, new ClientConnectEventArgs(socketServer.Information.RemoteAddress.CanonicalName));
                    }

                    PostSocketReadForServer(MAX_BUFFER_LENGTH);
                    Debug.WriteLine("Connection Received!");
                };
                await socketListener.BindServiceNameAsync(port);
                result = true;
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            return result;
        }

        public void SendMessageForServer(string message)
        {
            lock (this)
            {
                if (socketServer != null)
                {
                    SendMessageHelper(socketServer, message);
                }
                else
                {
                    Debug.WriteLine("serverSocket has not been setup. click listen to accept incoming connection and try again.");
                }
            }
        }

        public bool SendMessageForClient(string message)
        {
            bool result = true;
            lock (this)
            {
                if (socketClient != null)
                {
                    SendMessageHelper(socketClient, message);
                }
                else
                {
                    result = false;
                    Debug.WriteLine("Socket does not exist. Create another CCT enabled transport.");
                }
            }
            return result;
        }

        private void PostSocketReadForClient(int length)
        {
            try
            {
                Windows.Storage.Streams.Buffer readBuf = new Windows.Storage.Streams.Buffer((uint)length);
                var readOp = socketClient.InputStream.ReadAsync(readBuf, (uint)length, InputStreamOptions.Partial);
                readOp.Completed = (IAsyncOperationWithProgress<IBuffer, uint> asyncAction, AsyncStatus asyncStatus) =>
                {
                    switch (asyncStatus)
                    {
                        case AsyncStatus.Completed:
                        case AsyncStatus.Error:
                            try
                            {
                                IBuffer localBuf = asyncAction.GetResults();
                                uint bytesRead = localBuf.Length;
                                read = DataReader.FromBuffer(localBuf);

                                uint buffLen = read.UnconsumedBufferLength;
                                Debug.WriteLine("bytesRead: " + bytesRead + ", unconsumedbufflength: " + buffLen);

                                // check if buffLen is 0 and treat that as fatal error.
                                if (buffLen == 0)
                                {
                                    return;
                                }

                                // Perform minimal processing in the completion
                                string message = read.ReadString(buffLen);
                                Debug.WriteLine("Received Buffer : " + message);

                                // Post another receive to ensure future push notifications.
                                PostSocketReadForClient(MAX_BUFFER_LENGTH);
                            }
                            catch (Exception ex)
                            {
                                Debug.WriteLine(ex.Message);
                            }
                            break;
                        case AsyncStatus.Canceled:
                            break;
                    }
                };
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
        }

        private void PostSocketReadForServer(int length)
        {
            try
            {
                Windows.Storage.Streams.Buffer readBuf = new Windows.Storage.Streams.Buffer((uint)length);
                var readOp = socketServer.InputStream.ReadAsync(readBuf, (uint)length, InputStreamOptions.Partial);
                readOp.Completed = (IAsyncOperationWithProgress<IBuffer, uint> asyncAction, AsyncStatus asyncStatus) =>
                {
                    switch (asyncStatus)
                    {
                        case AsyncStatus.Completed:
                        case AsyncStatus.Error:
                            try
                            {
                                IBuffer localBuf = asyncAction.GetResults();
                                uint bytesRead = localBuf.Length;
                                read = DataReader.FromBuffer(localBuf);

                                uint buffLen = read.UnconsumedBufferLength;
                                Debug.WriteLine("bytesRead: " + bytesRead + ", unconsumedbufflength: " + buffLen);

                                // check if buffLen is 0 and treat that as fatal error.
                                if (buffLen == 0)
                                {
                                    return;
                                }

                                // Perform minimal processing in the completion
                                string message = read.ReadString(buffLen);
                                Debug.WriteLine("Received Buffer : " + message);

                                if (ClientSendEvent != null)
                                {
                                    ClientSendEvent(null, new ClientSendEventArgs(message, socketServer.Information.RemoteAddress.CanonicalName));
                                }

                                // Post another receive to ensure future push notifications.
                                PostSocketReadForServer(MAX_BUFFER_LENGTH);
                            }
                            catch (Exception ex)
                            {
                                Debug.WriteLine(ex.Message);
                            }
                            break;
                        case AsyncStatus.Canceled:
                            break;
                    }
                };
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
        }

        private async void SendMessageHelper(StreamSocket socket, string message)
        {
            try
            {
                if (write == null)
                {
                    write = new DataWriter(socket.OutputStream);
                }

                write.WriteString(message);
                Debug.WriteLine("sending message:  " + message);
                await write.StoreAsync();
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
        }
    }

    public class ClientSendEventArgs : EventArgs
    {
        public string Message;
        public string IPAddress;

        public ClientSendEventArgs(string message, string ipAddress)
        {
            Message = message;
            IPAddress = ipAddress;
        }

    }

    public class ClientConnectEventArgs : EventArgs
    {
        public string IPAddress;
        public ClientConnectEventArgs(string ipAddress)
        {
            IPAddress = ipAddress;
        }
    }
}

